/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package com.minidota.common;

import com.minidota.model.BaseObject;
import com.minidota.model.PointGraph;
import com.minidota.model.Vector2;
import com.minidota.settings.Settings;
import com.minidota.utils.Dijkstra;
import java.awt.Transparency;
import java.awt.image.*;
import java.io.*;
import java.util.ArrayList;
import javax.imageio.ImageIO;

/**
 *
 * @author custom
 */
public class Map {

    private final int id;
    private int height;
    private int width;
    private BufferedImage mapImage;
    public static final int COLOR_MASK = 0xff000000;
    public static final int FULL_OBSTACLE_COLOR = 0xff000000; //черный - полное препятсвие (нельзя пройти, нельзя стрелять)
    private static final int PARTIAL_OBSTACLE_COLOR = 0xffff0000; //красный - частичное препятсвие (нельзя пройти, можно стрелять)
    private static final int GREEN_COLOR = 0xff00ff00; //красный - частичное препятсвие (нельзя пройти, можно стрелять)
    private static final int FREE_COLOR = 0xffffffff; //белый - свободная точка
    private static final int[] COLOR_MAP = {FREE_COLOR, PARTIAL_OBSTACLE_COLOR, FULL_OBSTACLE_COLOR, GREEN_COLOR};
    public static final IndexColorModel COLOR_MODEL = new IndexColorModel(8, COLOR_MAP.length, COLOR_MAP, 0, true, Transparency.TRANSLUCENT, DataBuffer.TYPE_BYTE);
    private ArrayList<PointGraph> mapGraph;
    private int[][] mas;
    private int[][][] truePath; //массивы верных путей из врешины в вершину

    public Map(int id) {
        this.mapGraph = new ArrayList<PointGraph>();
        this.id = id;
        //достаем подложку
        File file = new File(Settings.SERVER_PATH + "maps/" + id + ".png");

        if (file.exists()) {
            try {
                BufferedImage img = ImageIO.read(file);
                if (img != null) {
                    BufferedImage binary_img = new BufferedImage(img.getWidth(), img.getHeight(), BufferedImage.TYPE_BYTE_BINARY, COLOR_MODEL);
                    binary_img.getGraphics().drawImage(img, 0, 0, null);
                    this.mapImage = binary_img;
                    this.height = img.getHeight();
                    this.width = img.getWidth();
                    //--graf
                    for (int nxInt = 1; nxInt < this.width; nxInt++) {
                        for (int nyInt = 1; nyInt < this.height; nyInt++) {
                            int color = mapImage.getRGB(nxInt, nyInt) | COLOR_MASK;
                            if (color == GREEN_COLOR) {
                                mapGraph.add(new PointGraph(nxInt, nyInt));
                            }
                        }
                    }
                    System.out.println("Map graf size: " + mapGraph.size());
                    this.mas = new int[mapGraph.size()][mapGraph.size()];
                    this.truePath = new int[mapGraph.size()][mapGraph.size()][mapGraph.size()];
                    for (int i = 0; i < mapGraph.size(); i++) {
                        for (int j = 0; j < mapGraph.size(); j++) {
                            Vector2 v = isSeeEachOther((int) mapGraph.get(i).getPosition().getX(), (int) mapGraph.get(i).getPosition().getY(), (int) mapGraph.get(j).getPosition().getX(), (int) mapGraph.get(j).getPosition().getY(), 10);
                            if (v == null && i != j) {
                                mas[i][j] = ((int) new Vector2(mapGraph.get(i).getPosition().getX() - mapGraph.get(j).getPosition().getX(), mapGraph.get(i).getPosition().getY() - mapGraph.get(j).getPosition().getY()).length());
                            }
                        }
                    }
                    for (int i = 0; i < mas.length; i++) {
                        for (int j = 0; j < mas[i].length; j++) {
                            for (int k = 0; k < mas[i].length; k++) {
                                truePath[i][j][k] = -1;
                            }
                            ArrayList<Integer> r = Dijkstra.getPath(mas, i, j);
                            int k = 0;
                            for (int n : r) {
                                truePath[i][j][k] = n;
                                k++;
                            }
                        }
                    }
                    /*ArrayList<Integer> r = Dijkstra.getPath(mas, 0, 5);
                     for (int n : r) {
                     System.out.print(n + ">");
                     }*/
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        } else {
            System.out.println("Map image not found !");
        }
    }
    private static ArrayList<Map> mapsList;

    private int getNumberNearbyPointGraph(Vector2 p) {
        int res = -1;
        int min = (int) this.mapGraph.get(0).getPosition().sub(p).lengthSquared();
        for (int m = 0; m < this.mapGraph.size(); m++) {
            int distance = (int) this.mapGraph.get(m).getPosition().sub(p).lengthSquared();
            if (distance <= min) {
                if (isSeeEachOther(this.mapGraph.get(m).getPosition(), p, 20) == null) {
                    res = m;
                    min = distance;
                }
            }
        }
        return res;
    }

    public ArrayList<Vector2> getPathFromTo(Vector2 from, Vector2 to) {
        ArrayList<Vector2> res = new ArrayList<Vector2>();
        int fromPointNumber = getNumberNearbyPointGraph(from);
        int toPointNumber = getNumberNearbyPointGraph(to);
        if (toPointNumber != -1 && fromPointNumber != -1) {
            for (int k = 0; k < truePath[fromPointNumber][toPointNumber].length; k++) {
                if (truePath[fromPointNumber][toPointNumber][k] != -1) {
                    res.add(this.mapGraph.get(truePath[fromPointNumber][toPointNumber][k]).getPosition());
                }
            }
            /*
             ArrayList<Integer> r = Dijkstra.getPath(mas, fromPointNumber, toPointNumber);
             for (int n : r) {
             System.out.print(n + " ");
             }*/
        }
        return res;
    }

    public static ArrayList<Map> getMapsList() {
        if (mapsList == null) {
            mapsList = new ArrayList<Map>();
            //mapsList.add(new Map(1));
        }
        return mapsList;
    }

    public static Map getMapById(int id) {
        for (Map map : getMapsList()) {
            if (map.id == id) {
                return map;
            }
        }
        Map newMap = new Map(id);
        mapsList.add(newMap);
        return newMap;
    }

    /**
     * @param nx - координата точки по x
     * @param ny - координата точки по y
     * @param r - расстояние до границы карты
     * @return возвращает true, если точка свободна для движения
     */
    public boolean isFreePoint(double nx, double ny) {
        int nxInt = (int) Math.round(nx);
        int nyInt = (int) Math.round(ny);
        if (withinTheBorders(nx, ny)) {
            int color = mapImage.getRGB(nxInt % width, nyInt % height) | COLOR_MASK;
            return color == FREE_COLOR;
        } else {
            return false;
        }
    }

    /**
     * @param nx - координата точки по x
     * @param ny - координата точки по y
     * @param r - расстояние до границы карты
     * @return возвращает true, если точка свободна для полета ракет/выстрелов
     */
    public boolean isFullObstaclePoint(double nx, double ny) {
        int nxInt = (int) Math.round(nx);
        int nyInt = (int) Math.round(ny);
        if (withinTheBorders(nx, ny)) {
            int color = mapImage.getRGB(nxInt % width, nyInt % height) | COLOR_MASK;
            return color == FULL_OBSTACLE_COLOR;
        } else {
            return true;
        }
    }

    /**
     * @param nx
     * @param ny
     * @return возвращает true, если точка (nx;ny) не выходит за пределы карты
     */
    public boolean withinTheBorders(double nx, double ny) {
        return (nx >= 0 && ny >= 0 && nx < width && ny < height);
    }

    /**
     * возвращает -1 если препятствий нет возвращает расстояние до препятствия,
     * если он есть
     */
    /*public int isSeeEachOther(User u1, User u2) {
     return isSeeEachOther((int) Math.round(u1.getX())
     ,(int) Math.round(u1.getY())
     ,(int) Math.round(u2.getX())
     ,(int) Math.round(u2.getY()));
     }*/
    /**
     *
     * @param v1 - точка откуда смотрят
     * @param v2 - точка куда смотрят
     * @param stepSize - шаг поиска препятствий
     * @return возвращает null, если препятствий нету, иначе возвращает точку,
     * где было найдено препятствие
     */
    public Vector2 isSeeEachOther(Vector2 v1, Vector2 v2, int stepSize) {
        return isSeeEachOther((int) Math.round(v1.getX()), (int) Math.round(v1.getY()), (int) Math.round(v2.getX()), (int) Math.round(v2.getY()), stepSize);
    }

    private Vector2 isSeeEachOther(int x1, int y1, int x2, int y2, int stepSize) {
        if (withinTheBorders(x1, y1)) {
            int xLength = Math.abs(x2 - x1);
            int yLength = Math.abs(y2 - y1);
            if (xLength >= yLength && xLength > 0) {
                double k = (y2 - y1) / (x2 - x1 + 0.0);
                double b = y1 - k * x1;
                int step = x2 > x1 ? stepSize : -stepSize;
                for (int x = x1 + step; step > 0 && x < x2 || step < 0 && x > x2; x += step) {
                    int y = (int) Math.round(k * x + b);
                    if (isFullObstaclePoint(x, y)) {
                        return new Vector2(x, y);
                    }
                }
            } else if (yLength > 0) {
                double k = (x2 - x1) / (y2 - y1 + 0.0);
                double b = x1 - k * y1;
                int step = y2 > y1 ? stepSize : -stepSize;
                for (int y = y1 + step; step > 0 && y < y2 || step < 0 && y > y2; y += step) {
                    int x = (int) Math.round(k * y + b);
                    if (isFullObstaclePoint(x, y)) {
                        return new Vector2(x, y);
                    }
                }
            }
            return null; //return -1;
        }
        return new Vector2(x1, y1); //return 0;
    }

    public double getCollisionCoef(BaseObject bo, Vector2 v1, Vector2 v2, int stepSize) {
        return getCollisionCoef(bo, (int) Math.round(v1.getX()), (int) Math.round(v1.getY()), (int) Math.round(v2.getX()), (int) Math.round(v2.getY()), stepSize);
    }

    private double getCollisionCoef(BaseObject bo, int x1, int y1, int x2, int y2, int stepSize) {
        if (withinTheBorders(x1, y1)) {
            int xLength = Math.abs(x2 - x1);
            int yLength = Math.abs(y2 - y1);
            if (xLength >= yLength && xLength > 0) {
                double k = (y2 - y1) / (x2 - x1 + 0.0);
                double b = y1 - k * x1;
                int step = x2 > x1 ? stepSize : -stepSize;
                for (int x = x1 + step; step > 0 && x < x2 || step < 0 && x > x2; x += step) {
                    int y = (int) Math.round(k * x + b);
                    if (!bo.isFreePoint(x, y)) {
                        return Math.abs((x2 - x + step) / (x2 - x1 + 0.0));
                    }
                }
            } else if (yLength > 0) {
                double k = (x2 - x1) / (y2 - y1 + 0.0);
                double b = x1 - k * y1;
                int step = y2 > y1 ? stepSize : -stepSize;
                for (int y = y1 + step; step > 0 && y < y2 || step < 0 && y > y2; y += step) {
                    int x = (int) Math.round(k * y + b);
                    if (!bo.isFreePoint(x, y)) {
                        return Math.abs((y2 - y + step) / (y2 - y1 + 0.0));
                    }
                }

            }
            return 0;
        }
        return 1;
    }

    /**
     * @return the height
     */
    public int getHeight() {
        return height;
    }

    /**
     * @return the width
     */
    public int getWidth() {
        return width;
    }
}
